/*
    Copyright (C) 2010  Ping Shin Ching

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses.

	Ping Shin Ching
	righteous.ninja@gmail.com OR ping_sc@hotmail.com
 */

#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>

#ifdef WINDOWS

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")

#else

#include <resolv.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

#endif

#include "xsocket.h"

static short stsocket_strace;


/*************************************************************************
 * Inline Wrapper for getting the last socket error
 *************************************************************************/
inline int GetSocketError(){
#ifdef WINDOWS
	return WSAGetLastError();
#else
	return errno;
#endif
}


/*************************************************************************
 * Initialize Socket support - Required only for Windows
 *************************************************************************/
int InitializeSocketSupport(int trace){
	stsocket_strace = trace;

#ifdef WINDOWS

	unsigned short wVersionRequested;
	WSADATA wsaData;
	int err;
	 
	wVersionRequested = MAKEWORD( 2, 2 );
 
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 || ( LOBYTE( wsaData.wVersion ) != 2 ||
		    HIBYTE( wsaData.wVersion ) != 2 )){
	    sprintf(tmps,"SCK: Could not find useable sock dll\n");
	    SocketTrace(tmps);
		return WSAGetLastError();
	}
	else
		return 0;
	
#else
	SocketTrace("Init socket support is OK");
	return 0;
#endif
}


/*************************************************************************
 * Legacy Windows support
 *************************************************************************/
void CleanupSocketSupport(){
#ifdef WINDOWS
	WSACleanup( );
#else

#endif
}


/*************************************************************************
 * Initialize socket and set in non-blocking mode
 *************************************************************************/
int InitSocket(int* hsock, int l_kind){
	int *p_int, *p_int_no ;
	char tmps[128];

	*hsock = socket(AF_INET, SOCK_STREAM, 0);

	if(*hsock == -1){
		sprintf(tmps,"Error socket %d",GetSocketError());
		SocketTrace(tmps);
		return GetSocketError();
	}
	
	p_int 	= (int*)malloc(sizeof(int));
	p_int_no= (int*)malloc(sizeof(int));

	*p_int = 1;
	*p_int_no = 0;
		
	if( (setsockopt(*hsock, SOL_SOCKET, SO_REUSEADDR, (char*)p_int, sizeof(int)) == -1 )||
		(setsockopt(*hsock, IPPROTO_TCP, TCP_NODELAY, (char*)p_int, sizeof(int)) == -1 ) || 
		(setsockopt(*hsock, SOL_SOCKET, SO_KEEPALIVE, (char*)p_int, sizeof(int)) == -1 )){
		sprintf(tmps,"Error opt %d", GetSocketError());
		SocketTrace(tmps);
		free(p_int);
		free(p_int_no);
		return GetSocketError();	
	}
	free(p_int);
	free(p_int_no);


#ifdef WINDOWS
	unsigned long t_long = 2;
	if(ioctlsocket(*hsock, FIONBIO, & t_long)!=0){
		sprintf(tmps, "Error conn%ld",err = WSAGetLastError());
		SocketTrace(tmps);
		return err;
	}
	return 0;
#else

	if(fcntl(*hsock, F_SETFL, O_NONBLOCK)==-1){
		sprintf(tmps, "Error conn %d",errno );
		SocketTrace(tmps);
		return errno;
	}
	return 0;
#endif
}


/*************************************************************************
 * Connect to the socket
 *************************************************************************/
int ConnectSocket(int* hsock, const char * host_name, const long host_port, int l_kind, int backlog){
	return ConnectSocket2( hsock, 0, host_name, host_port, l_kind, backlog);
}

int ConnectSocket2(int* hsock, const char* bind_name, const char * host_name, const long host_port, int l_kind, int backlog){
	//For the remote
	struct sockaddr_in	my_addr;
	struct sockaddr_in6	my_addr6;

	//For the bind
	struct sockaddr_in	my_Baddr;
	struct sockaddr_in6	my_Baddr6;

	char tmps[128];
	char ipv6	= 0;
	const char* type;

	my_addr.sin_family = AF_INET ;
	my_addr6.sin6_family = AF_INET6;
	my_Baddr.sin_family = AF_INET ;
	my_Baddr6.sin6_family = AF_INET6;


	my_addr.sin_port = htons(host_port);
	my_addr6.sin6_port = htons(host_port);



	if(l_kind==SOCK_SERVER){
		my_Baddr.sin_port = htons(host_port);
		my_Baddr6.sin6_port = htons(host_port);
		type="server";
	}
	else{
		my_Baddr.sin_port = 0;
		my_Baddr6.sin6_port = 0;
		type="client";
	}	

	memset(&(my_addr.sin_zero), 0, 8);

		if (!bind_name){
			//no address to just bind to any
			my_Baddr.sin_addr.s_addr = INADDR_ANY;
			if( bind( *hsock, (struct sockaddr*)&my_Baddr, sizeof(my_Baddr)) == -1 ){
			 	SocketTrace2("Error conn (%s) %d",type, GetSocketError());
				return GetSocketError();
			}
			else
				SocketTrace2("Bound INADDR_ANY");
		}
		else{
			if(inet_pton(AF_INET6, bind_name, &(my_Baddr6.sin6_addr))==1){
				if( bind( *hsock, (struct sockaddr*)&my_Baddr6, sizeof(my_Baddr6)) == -1 ){
				 	SocketTrace2("Error conn (%s) (ipv6) %d", type,GetSocketError());
					return GetSocketError();
				}
				SocketTrace2("Bound ipv6 %s", type);
			}
			else if(inet_pton(AF_INET, bind_name, &my_Baddr.sin_addr)==1){
				if( bind( *hsock, (struct sockaddr*)&my_Baddr, sizeof(my_Baddr)) == -1 ){
				 	SocketTrace2("Error conn (%s) (ipv4) %d", type,GetSocketError());
					return GetSocketError();
				}
				SocketTrace2("Bound ipv4 %s", type);
			}
			else{
				SocketTrace2("Error conn %s no valid ip %d",type, GetSocketError());
			}
		}



	switch(l_kind){
	
	case SOCK_SERVER:
		/*
		if( bind( *hsock, (struct sockaddr*)&my_addr, sizeof(my_addr)) == -1 ){
		 	sprintf(tmps,"Error conn sock_svr %d",GetSocketError());
			SocketTrace(tmps);
			return GetSocketError();
		}
		*/
		if(listen( *hsock, backlog) == -1 )	{
			sprintf(tmps,"Error listen %d",GetSocketError());
			SocketTrace(tmps);
			return GetSocketError();
		}
		break;

	case SOCK_0:
		//my_addr.sin_addr.s_addr = inet_addr(host_name);
		my_addr.sin_addr.s_addr = inet_addr(host_name);
		if( connect( *hsock, (struct sockaddr*)&my_addr, sizeof(my_addr)) == -1 ){
#ifdef WINDOWS
			if((err = WSAGetLastError())== WSAEWOULDBLOCK)
				return 0;
			else
				return WSAGetLastError();
#else
			if(errno == EINPROGRESS)
				return 0;
			else
				return errno;
#endif

		}
		break;
	default:
		return -1;		
	}

	sprintf(tmps, "Connect socket is good to go %d", *hsock);
	SocketTrace(tmps);
	return 0;
}


/*************************************************************************
 * Close the socket
 *************************************************************************/
void CloseSocket(int* sock){
#ifdef WINDOWS
	closesocket(*sock);
#else
	close(*sock);
#endif
}


/*************************************************************************
 * Printout socket messages
 *************************************************************************/
void SocketTrace(const char* msg){
	if(stsocket_strace)
		printf("SCKX: %s\n", msg);	

}


void SocketTrace2(const char* format, ...){
        va_list ap;
	char msg128[128];
	int len;

	sprintf(msg128, "SCKX: ");
	len=strlen(msg128);
	if(stsocket_strace){
        	va_start(ap, format);
        	vsnprintf(msg128+len, 128-len, format, ap);
        	va_end(ap);
		printf("%s\n", msg128);
	}
}



